Necesitamos acciones cuando las funciones no son suficientes para nuestros propósitos. La necesidad de crear una acción nos surge en el siguiente programa:
#include <iostream> using namespace std; int main() { int h, m; char c; cin >> h >> c >> m; m = m + 1; if (m >= 60) { m = 0; h = h + 1; if (h >= 24) { h = 0; } } if (h < 10) cout << '0'; cout << h; cout << ':'; if (m < 10) cout << '0'; cout << m; }
Básicamente, queremos transformar en un subprograma la parte donde se incrementa el valor de m y h. Esencialmente, queremos poder invocar fácilmente la instrucción incrementa_hora para un par de variables cualquiera (así será más útil).
De todas maneras, antes podemos convertir en acción (para repasar el tema Acciones 1`) la parte que muestra el entero h en 2 dígitos:
if (h < 10) cout << '0'; cout << h;
Esta parte está repetida para la variable m y si la convertimos en función simplificaremos el programa. La acción será:
void en2digitos(int n) { if (n < 10) cout << '0'; cout << n; }
que utiliza una variable n genérica. Como que la utilidad de en2digitos es solamente mostrar algo por la pantalla, no devuelve ningún valor y de ahí el void y la absencia de un return. Ahora la parte de mostrar la hora en el programa principal es:
en2digitos(m); cout << ':'; en2digitos(h);
No ahorramos mucho codigo pero éste se ha algo más comprensible.
Pero volvamos a nuestro objetivo inicial, que era hacer una función incrementa_hora. Podemos empezar por poner las instrucciones que queremos sustituir dentro de la función y luego modificarlas hasta conseguir nuestro objetivo:
????? incrementa_hora(int H, int M) { // corta y pega directo... m = m + 1; if (m >= 60) { m = 0; h = h + 1; if (h >= 24) { h = 0; } } }
Hemos dejado el tipo de retorno en interrogante porque no sabemos qué poner ahora mismo, lo podemos arreglar luego. Ahora habría que adaptar un poco el cuerpo de la función porque en incrementa_hora hemos puesto H y M en vez de h y m. Por otro lado, si de momento no devolvemos nada, podemos poner el tipo void en los interrogantes. Quedaría así, entonces:
void incrementa_hora(int H, int M) { M = M + 1; if (M >= 60) { M = 0; H = H + 1; if (H >= 24) { H = 0; } } }
La llamada en el programa principal será así:
incrementa_hora(h, m);
que sustituye a las instrucciones que hemos cortado. El programa entero ahora mismo es este:
#include <iostream> using namespace std; void incrementa_hora(int H, int M) { M = M + 1; if (M >= 60) { M = 0; H = H + 1; if (H >= 24) { H = 0; } } } int main() { int h, m; char c; cin >> h >> c >> m; incrementa_hora(h, m); en2digitos(h); cout << ':'; en2digitos(m); }
Si pruebas el programa verás que no funciona. Pero aparentemente, debería.
La razón de que no funcione el programa es que las variables h y m del main son diferentes de las variables H y M de la función incrementa_hora. En general, cuando se declaran los parámetros de una función, éstos se tratan como variables en el cuerpo de la función pero se asume que se inicializan con el valor de entrada.
Ahí está el problema: H y M son variables de incrementa_hora y se inicializan con los valores de h y m. Por lo tanto, cuando en la primera línea de incrementa_hora hacemos:
M = M + 1;
qué variable se incrementa? Pues claramente, M. Pero nosotros quisieramos que en el main la variable m sufriese el mismo cambio, porque nuestro objetivo es la hora del programa principal. Es decir, el código original modificaba h y m en el main y ahora esas modificaciones no se estan produciendo porque el paso de los parámetros se hace copiando su valor en H y M.
Este tipo de paso de parámetros, que es el típico de las funciones, se denomina "paso por valor".
Qué otras opciones hay, entonces? Pues la otra es añadir el símbolo & al lado del tipo int en H y M así:
void incrementa_hora(int& H, int& M) { ...
Este pequeño cambio produce un cambio muy sustancial de la forma en como se llama la función incrementa_hora. Ahora, no existen variables H y M independientes, sino que en el momento de invocar a la función desde el main así:
incrementa_hora(h, m);
se establece un vínculo temporal (durante la ejecución de incrementa_hora), que hace que H se refiera a h y M se refiera a m. Que H se refiera a h significa que todos los cambios que hagamos en H realmente se estarán haciendo en h, esencialmente asignar nuevos valores, incrementar, y todas las operaciones que puedan sustituir el valor de H por uno nuevo.
Por tanto, con el vínculo vigente durante la ejecución de incrementa_hora se ejecutarán instrucciones equivalentes a las que habíamos puesto inicialmente y con el mismo efecto: se incrementará h y m teniendo en cuenta que son unas horas y unos minutos. Durante el tiempo que dura la ejecución de incrementa_hora, en el cuerpo de la función, allí donde pone H uno puede ver h y allí donde pone M se puede ver m. Esta forma de pasar parámetros se denomina "paso por referencia" porque no se copia el valor sino que se establece un vínculo con (o se dice que el parámetro "se refiere a") la variable original.
Ahora se ve claro que void es el tipo de retorno apropiado para incrementa_hora. No tiene que devolver nada, porque su misión es dejar los valores finales guardados en las variables H y M (con independencia de las variables a las que se estén refiriendo).
Los subprogramas que modifican valores que les son ajenos (porque reciben parámetros pasados por referencia), como es el caso de incrementa_hora, se denominan acciones y no funciones. La existencia del tipo de retorno void suele ser un indicador claro (aunque no el único) de que estamos delante de una acción. La definición completa se da en el tema Definición de Acción.
En preparación